/*
 * @Desc:
 * @Author: LIUBIN
 * @version: 0.1
 * @Date: 2025-06-01 17:58:29
 */
#ifdef __cplusplus
extern "C" {
#endif

#include "porting/lv_porting.h"
#include "lvgl.h"

#define USE_GB
#define USE_KEYBOARD

#ifdef USE_GB
#include "gb_port.h"
#include "gb.h"
#include "hardware.h"
#endif
/* nes demo */
#ifdef USE_NES
#include "lv_demo_nes.h"
#include "SuperMario.h"
#include "nes.h"
#endif // DEBUG

#include "lv_demo_gba.h"

#ifdef __cplusplus
}
#endif // DEBUG

#include <stdio.h>
#include <stdlib.h>
#include <SDL2/SDL.h>

#ifdef USE_GB
extern int audio_open(struct gameboy *gb);
extern int audio_write(struct gameboy *gb, uint8_t *data, uint32_t size);

static SDL_Window *window     = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture   = NULL;
static uint16_t *canvas_buf;
int video_open(struct gameboy *gb)
{
    (void)gb;

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) != 0) {
        SDL_Log("SDL_Init(): %s", SDL_GetError());
        return 0;
    }

    // 创建 canvas 缓冲区（160x144 像素）
    canvas_buf = malloc(LV_CANVAS_BUF_SIZE_TRUE_COLOR(160, 144));
    if (!canvas_buf) {
        printf("Failed to allocate canvas buffer\n");
        return 0;
    }

    // 创建 canvas 对象
    my_gba = lv_canvas_create(lv_scr_act()); // 创建在当前 screen 上
    lv_obj_set_size(my_gba, 160 * 2, 144 * 2);
    // lv_canvas_set_buffer(my_gba, canvas_buf, 160, 144, LV_IMG_CF_TRUE_COLOR);
    lv_obj_align(my_gba, LV_ALIGN_CENTER, 0, 0); // 居中显示
    lv_img_set_zoom(my_gba, 256);                // 两倍分辨率
    return 1;
}

#define GB_WIDTH      160
#define GB_HEIGHT     144
#define SCALE         2
#define RENDER_WIDTH  (GB_WIDTH * SCALE)
#define RENDER_HEIGHT (GB_HEIGHT * SCALE)

// canvas_buf_2x 大小应为 320 x 288
static uint16_t canvas_buf_2x[RENDER_WIDTH * RENDER_HEIGHT];

int video_write(struct gameboy *gb, uint8_t *data, uint32_t size)
{
    static const uint16_t palette[] = {
        0xFFFF, // 白色   R=31, G=63, B=31
        0xBDF7, // 浅灰   R=23, G=47, B=23
        0x73AE, // 深灰   R=14, G=28, B=14
        0x0000  // 黑色
    };
    (void)size;

    if (gb->screenshot) {
        screenshot(gb, "screenshot.png");
        gb->screenshot = false;
    }

    if (gb->testing) {
        return 1;
    }

    // 进行 2x 放大渲染
    for (int y = 0; y < GB_HEIGHT; y++) {
        for (int x = 0; x < GB_WIDTH; x++) {
            uint8_t px     = data[y * GB_WIDTH + x];
            uint16_t color = palette[px];

            int dx = x * SCALE;
            int dy = y * SCALE;

            // 写入 2x2 像素块
            canvas_buf_2x[(dy + 0) * RENDER_WIDTH + (dx + 0)] = color;
            canvas_buf_2x[(dy + 0) * RENDER_WIDTH + (dx + 1)] = color;
            canvas_buf_2x[(dy + 1) * RENDER_WIDTH + (dx + 0)] = color;
            canvas_buf_2x[(dy + 1) * RENDER_WIDTH + (dx + 1)] = color;
        }
    }

    // 设置 canvas buffer：大小为 320x288
    lv_canvas_set_buffer(my_gba, canvas_buf_2x, RENDER_WIDTH, RENDER_HEIGHT, LV_IMG_CF_TRUE_COLOR);

    return 1;
}

#endif

int main(int argc, char *argv[])
{

    lv_porting_init();

#ifdef USE_GB
    SDL_Init(SDL_INIT_AUDIO);
    gbmu_port_init();

    static struct gameboy gameboy = {0};
    struct gameboy *gb            = &gameboy;
    static int fd, i, ret;
    struct stat stat_buf;

    (void)testing_dummy_setup(gb);
    if (argc == 2) {
        gb->rom_file = argv[argc - 1];
    }
    if (argc == 1) {
        gb->rom_file = "BKM.gb";
    }
    if ((fd = open(gb->rom_file, O_RDONLY | O_BINARY)) == -1) {
        (void)fprintf(stderr, "failed to open file\n");
        return 1;
    }

    if (fstat(fd, &stat_buf) != 0) {
        (void)close(fd);
        (void)fprintf(stderr, "fstat() failed\n");
        return 1;
    }

    if ((stat_buf.st_size < 0x8000) ||
        (stat_buf.st_size > 0x800000) ||
        (stat_buf.st_size & 0x7fff)) {
        (void)close(fd);
        (void)fprintf(stderr, "invalid file\n");
        return 1;
    }

    gb->cartridge.data = malloc(stat_buf.st_size);
    gb->cartridge.size = stat_buf.st_size;
    ret                = read(fd, gb->cartridge.data, gb->cartridge.size);
    (void)close(fd);

    if (ret != stat_buf.st_size) {
        (void)fprintf(stderr, "read() failed\n");
        (void)free(gb->cartridge.data);
        return 1;
    }

    (void)memcpy(gb->memory, gb->cartridge.data, 0x8000);
    (void)io_init(gb);

    if (!mbc_init(gb)) {
        return 1;
    }

    video_open(gb);
    audio_open(gb);

    while (!gb->done) {
        (void)dma_update(gb);
        gb->done |= cpu_update(gb) < 0;

        if (lcd_update(gb)) {
            (void)video_write(gb, gb->lcd.buf, VIDEO_BUF_SIZE);
            (void)input_read(gb);
        }

        if (timer_update(gb)) {
            (void)sequencer_step(gb);
        }

        (void)serial_update(gb, 0);

        if (sound_update(gb)) {
            (void)audio_write(gb, gb->sound_buf, SOUND_BUF_SIZE);
            (void)rtc_tick(gb);
        }

        if (gb->testing) {
            (void)gb->testing_run_hook(gb);
        }

        gb->cycles += 4;
    }

    return 0;
#endif
#ifdef USE_NES
    lv_demo_nes();

    nes_t *nes = nes_init();
    if (argc == 1) {
        // int ret = nes_load_file(nes, nes_file_path);
        int ret = nes_load_rom(nes, acSuperMario);
        if (ret) {
            NES_LOG_ERROR("nes load rom fail\n");
            goto error;
        }
        nes_run(nes);
        nes_unload_rom(nes);
        nes_deinit(nes);

        return 0;
    }

    if (argc == 2) {
        const char *nes_file_path = argv[1];
        size_t nes_file_path_len  = strlen(nes_file_path);
        if (nes_memcmp(nes_file_path + nes_file_path_len - 4, ".nes", 4) == 0 || nes_memcmp(nes_file_path + nes_file_path_len - 4, ".NES", 4) == 0) {
            NES_LOG_INFO("nes_file_path:%s\n", nes_file_path);

            int ret = nes_load_file(nes, nes_file_path);
            // int ret = nes_load_rom(nes, acSuperMario);
            if (ret) {
                NES_LOG_ERROR("nes load file fail\n");
                goto error;
            }
            nes_run(nes);
            nes_unload_file(nes);
            nes_deinit(nes);
            return 0;
        } else {
            goto error;
        }
    } else {

        return 0;
    }
error:
    nes_deinit(nes);
    getchar();
    return -1;
#endif
}
